package com.springframework.core.context;

import com.springframework.core.annotation.MyAutowired;
import com.springframework.core.annotation.MyComponent;
import com.springframework.core.annotation.MyComponentScan;
import com.springframework.core.annotation.MyScope;
import com.springframework.core.constant.ScopeType;
import com.springframework.core.exception.NotSuchBeanException;
import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 纯手写一个Spring框架（基于注解模式）
 * @author youzhengjie
 */
public class MyAnnotationConfigApplicationContext implements ApplicationContext {

    //bean定义池
    private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap;

    //单例池
    private ConcurrentHashMap<String,Object> singletonMap;

    //BeanPostProcessor集合
    private CopyOnWriteArrayList<BeanPostProcessor> beanPostProcessors;

    //私有构造器
    private MyAnnotationConfigApplicationContext(){

    }

    public MyAnnotationConfigApplicationContext(final Class<?> configClass) {

        try {
            //判断扫描类上是否有MyComponentScan注解存在
            if (configClass.isAnnotationPresent(MyComponentScan.class)) {

                this.beanDefinitionMap=new ConcurrentHashMap<>();
                this.singletonMap=new ConcurrentHashMap<>();
                this.beanPostProcessors=new CopyOnWriteArrayList<>();

                //获取MyComponentScan注解对象
                MyComponentScan componentScan = configClass.getAnnotation(MyComponentScan.class);
                //获取MyComponentScan注解的值
                // com.springframework.core.service
                String scc = componentScan.value();
                // 转换成 com/springframework/core/service
                String sc = scc.replace(".", "/");
                ClassLoader classLoader = this.getClass().getClassLoader();
                //这种写法可以防止Java把空格路径转换成%20而导致找不到文件夹
                String uri = Objects.requireNonNull(classLoader.getResource(sc)).toURI().getPath();
                String scp = uri.substring(1, uri.length());
                File dir = new File(scp);
                //如果指定的包路径正确，则扫描这个包的class文件，然后加载到beanDefinitionMap中
                if (dir.isDirectory()) {
                    File[] files = dir.listFiles();
                    if(files!=null&&files.length>0){

                        for (File file : files) {
                            //暂时不做这个处理。这个可以递归实现
//                            if(file.isDirectory()){
//
//                            }
                            //我们只找后缀名为.class的文件
                            if(file.getName().endsWith(".class")){
                                //取类名
                                String cn = file.getName().substring(0,file.getName().length()-6);
                                //全类名
                                String className=scc+"."+cn;
                                //通过classloader类加载器去加载类，获取类实例
                                Class<?> aClass = classLoader.loadClass(className);
                                //如果不是接口并且还有MyComponent注解就把这个bean放到beanDefinitionMap中
                                if(!aClass.isInterface()&&aClass.isAnnotationPresent(MyComponent.class)) {

                                    //判断完如果有MyComponent注解之后立马判断该类是否是BeanPostProcessor的实现类
                                    if(BeanPostProcessor.class.isAssignableFrom(aClass)){
                                        //然后创建这个对象
                                        BeanPostProcessor obj = (BeanPostProcessor) aClass.getConstructor().newInstance();
                                        //放入BeanPostProcessor集合中
                                        beanPostProcessors.add(obj);
                                    }
                                    BeanDefinition beanDefinition = new BeanDefinition();
                                    //获取类名
                                    String simpleName = aClass.getSimpleName();
                                    //把类名转换成bean名，也就是首字母小写的name
                                    String beanName = Introspector.decapitalize(simpleName);
                                    //beanDefinition的class类型
                                    beanDefinition.setType(aClass);
                                    //是单例还是多例
                                    //判断是否有MyScope注解
                                    if(aClass.isAnnotationPresent(MyScope.class)){
                                        //如果有，则获取值
                                        MyScope myScope = aClass.getAnnotation(MyScope.class);
                                        //取注解值
                                        String sv = myScope.value();
                                        //为null则报错
                                        sv = Objects.requireNonNull(sv);
                                        //如果是多例
                                        if(sv.equals(ScopeType.PROTOTYPE)){
                                            //设置为多例即可
                                            beanDefinition.setScope(ScopeType.PROTOTYPE);
                                        }else {//如果写了单例或者没写、又或者写错了全都作为单例
                                            //设置成单例即可
                                            beanDefinition.setScope(ScopeType.SINGLETON);
                                        }
                                    }
                                    //不管是单例还是多例都放到beanDefinitionMap中，等待下面的处理
                                    beanDefinitionMap.put(beanName,beanDefinition);
                                }
                            }
                        }

                    }

                }
                //到这里就扫描结束了，beanDefinitionMap已经存储了所有的组件信息

                /*
                 * 找出单例并把它生成bean对象放入单例池中
                 */
                ConcurrentHashMap.KeySetView<String, BeanDefinition>
                        beanDefinitionKeySetView = beanDefinitionMap.keySet();

                //遍历出beanName
                for (String beanName : beanDefinitionKeySetView) {
                    BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
                    String scope = beanDefinition.getScope();
                    if(scope!=null&&scope.equals(ScopeType.PROTOTYPE)){ //多例则不管它
                        //不做任何事
                    }else {
                        //如果是单例的，我们就要去单例池找一下，看看有没有
                        if(!singletonMap.containsKey(beanName)){//如果单例池没有这个对象就要创建了
                            //创建对象
                            Object obj = this.createBean(beanName);
                            //放入单例池
                            singletonMap.put(beanName,obj);
                        }
                    }

                }

            }else {
                throw new RuntimeException("指定配置类上没有MyComponentScan注解存在");
            }
        }catch (URISyntaxException e1){
            e1.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建bean对象
     * bean的生命周期：推断构造方法(这里我全都采用无參构造)->创建bean对象->DI依赖注入->Aware回调(BeanName和BeanFactory)->初始化前->初始化->初始化后（AOP）
     * @param beanName
     * @return
     */
    private Object createBean(String beanName) {


        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        //由于对象的BeanDefinition全都在加载阶段放到beanDefinitionMap中了，如果通过beanName拿不到说明根本就没有这个bean
        if(beanDefinition==null){
            throw new NotSuchBeanException("beanDefinitionMap没有该bean的定义");
        }else {
            Class<?> aClass = beanDefinition.getType();
            try {
                //通过无參构造
                Object obj = aClass.getConstructor().newInstance();

                //DI依赖注入
                //获取全部字段属性----一定要这个getDeclaredFields，不然private获取不到
                Field[] fields = aClass.getDeclaredFields();
                for (Field field : fields) {

                    //判断该字段是否有@MyAutowired注解
                    if(field.isAnnotationPresent(MyAutowired.class)){

                        //如果有这个注解的话则进行依赖注入

                        //进行爆破，使我们可以操作private属性
                        field.setAccessible(true);

                        //根据byName模式拿到bean并且注入
                        Object bean = getBean(field.getName());

                        //DI依赖注入
                        field.set(obj,bean);
                    }

                }

                //判断对象是否实现了Aware接口
                if(obj instanceof Aware){

                    //因为Aware回调接口有很多，所以我们要具体判断是哪个Aware
                    //先找BeanNameAware
                    if(obj instanceof BeanNameAware){
                        //具体干什么Spring不管，只负责调用
                        ((BeanNameAware) obj).setBeanName(beanName);
                    }
                    //....可以更多自己想要的Aware，比如BeanFactoryAware

                }
                //处理器-before
                if(beanPostProcessors!=null&&beanPostProcessors.size()>0){
                    for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                        obj = beanPostProcessor.postProcessorBeforeInitialization(obj, beanName);
                    }
                }
                //初始化bean
                if(obj instanceof InitializingBean){
                    ((InitializingBean) obj).afterPropertiesSet();
                }
                //处理器-after
                if(beanPostProcessors!=null&&beanPostProcessors.size()>0){
                    for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
                       obj=beanPostProcessor.postProcessorAfterInitialization(obj,beanName);
                    }
                }

                return obj;
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
            return null;
        }
    }


    @Override
    public Object getBean(String beanName) {

        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if(beanDefinition==null){
//            throw new NotSuchBeanException("beanDefinitionMap没有该bean的定义");
            return null;
        }else {
            //判断是单例还是多例
            String scope = beanDefinition.getScope();
            //如果是单例的话
            if(scope!=null&&scope.equals(ScopeType.PROTOTYPE)){
                //多例则每次都创建对象
                Object obj = this.createBean(beanName);
                return obj;
            }else {
                //如果是单例的，我们就要去单例池找一下，看看有没有
                //如果有的话就返回这个单例池对象即可
                if(singletonMap.containsKey(beanName)){
                    //返回bean对象
                    return singletonMap.get(beanName);
                }else { //如果单例池没有这个对象就要创建了
                    //创建对象
                    Object obj = this.createBean(beanName);
                    //放入单例池
                    singletonMap.put(beanName,obj);
                    return obj;
                }
            }
        }
    }
}
